#ifdef _MSC_VER
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#endif

#include <time.h>
#include "os/platform.h"
#include "utils/nstring.h"
#include "utils/cmdline.h"
#include "main/result.h"
#include "main/benchmark.h"
#include "utils/nstring.h"
#include "modules/pbmemory.h"
#include "modules/pbalgorithm.h"
#include "modules/pbthreads.h"
#include "modules/pbcpu.h"
#include "modules/pbnet.h"


using namespace std;


#define APP_VERSION_STRING      "v0.5.2"


static void WriteOutput(CResultManager *rm)
{
    const BENCHMARK_TOTAL *total = rm->GetResultTotal();
    const vector<BENCHMARK_MODULE> *modules = rm->GetResultModules();
    const vector<BENCHMARK_NODE> *nodes = rm->GetResultNodes();

    puts("\nOsszesen");

    for (unsigned i=0; i<total->filenames.size(); i++)
        printf("%8.2f%% (%.3fs, %.2f) %s\n", total->values[i] * 100.0f, total->times[i], total->weighted_times[i], total->filenames[i].c_str());

    puts("\nModulonkent");

    for (unsigned i=0; i<modules->size(); i++)
    {
        const BENCHMARK_MODULE *m = &(*modules)[i];
        printf("    <%s>\n", m->name.c_str());

        for (unsigned j=0; j<total->filenames.size(); j++)
            printf("    %8.2f%% (%.3fs, %.2f) %s\n", m->values[j] * 100.0f, m->times[j], m->weighted_times[j], total->filenames[j].c_str());
    }

    puts("\nReszeredmenyenkent");

    for (unsigned i=0; i<nodes->size(); i++)
    {
        const BENCHMARK_NODE *n = &(*nodes)[i];

        printf("    %s\n", n->name.c_str());

        for (unsigned j=0; j<total->filenames.size(); j++)
            printf("    %8.2f%% %11.8f %s\n", n->values[j] * 100.0f, n->times[j], total->filenames[j].c_str());
    }
}


static void WriteCSV(CResultManager *rm)
{
    const BENCHMARK_TOTAL *total = rm->GetResultTotal();
    const vector<BENCHMARK_MODULE> *modules = rm->GetResultModules();
    const vector<BENCHMARK_NODE> *nodes = rm->GetResultNodes();

    puts("\"Osszesen\",");

    for (unsigned i=0; i<total->filenames.size(); i++)
        printf("%.2f%%,\"%s\"\n", total->values[i] * 100.0f, total->filenames[i].c_str());

    puts(",\n\"Modulonkent\",");

    for (unsigned i=0; i<modules->size(); i++)
    {
        const BENCHMARK_MODULE *m = &(*modules)[i];
        printf("\"%s\",\n", m->name.c_str());

        for (unsigned j=0; j<total->filenames.size(); j++)
            printf(",%.2f%%,\"%s\"\n", m->values[j] * 100.0f, total->filenames[j].c_str());
    }

    puts(",\n\"Reszeredmenyenkent\",");

    for (unsigned i=0; i<nodes->size(); i++)
    {
        const BENCHMARK_NODE *n = &(*nodes)[i];

        printf("\"%s\",\n", n->name.c_str());

        for (unsigned j=0; j<total->filenames.size(); j++)
            printf(",%.2f%%,\"%s\"\n", n->values[j] * 100.0f, total->filenames[j].c_str());
    }
}


static void SetupInfo(RESULTSFILEINFO *info, const CCmdlineArgs *args)
{
    os_GetOSName(&info->os);

    double start = os_GetTime();
    double current;

    while ((current=os_GetTime()) == start)
        ;

    info->gettime_precision = ft("%.12f", current - start);

    info->display_name = args->GetValue("-name", "anonymus");
    info->comment = args->GetValue("-comment", "");

    time_t currenttime;
    time(&currenttime);
    info->created = asctime(localtime(&currenttime));
    if (info->created.GetLast() == '\n')  // az asctime() \n -t ir a vegere
        info->created.RemoveLast();

    os_GetCompiler(&info->compiler);
    os_GetCRuntime(&info->lib);

    os_GetHw(&info->hw);
}


static void PrintHelp()
{
    printf("Platform Benchmark " APP_VERSION_STRING ", pb verzio: %d Salamon Tibor\n\n", PBENCH_VERSION);
    puts("A program celja, hogy ket szoftver kornyezetet, nem pedig ket");  // TODO: ez mekkora bena szoveg :P
    puts("hardvert hasonlitson ossze, de utobbira is alkalmas.");
    puts("\nTeszteli az: oprendszer, hardver, fordito, C runtime library, STL egyutteset");
    puts("Negy funkcio, ebbol kettot hasznalsz altalaban:");
    puts("1. teszt mod");
    puts("  pbench [-n SZAM] [-q] [-name NEV] [-comment COMMENT] [-o FAJLNEV]");
    puts("2. eredmenyek osszehasonlitasa (-diff kapcsolo)");
    puts("  pbench [-cfg KONFIGAJL] [-csv] -diff FAJLNEV1 FAJLNEV2 [FAJLNEV3]...");
    puts("3. config fajl irasa (-genconf kapcsolo)");
    puts("  pbench -genconf");
    puts("  (erre valoszinuleg sosem lesz szukseged)");    
    puts("4. generalt config fajlok osszefesulese/atlagolasa (-mc kapcsolo)");
    puts("  pbench -mc KONFIGAJL1 KONFIGAJL2 [KONFIGFAJL3]...");
    puts("  (erre valoszinuleg sosem lesz szukseged)\n");

    puts("Ket vagy tobb eredmenyt tobb szempontbol is ossze lehet hasonlitani.");
    puts("Nincs abszolut pontszam, mindig ket eredmeny aranyat mutatja.");

    puts("\nHa a -diff kapcsolot hasznalod, akkor osszehasonlit, kulonben kiirja a ");
    puts("szabvanyos kimenetre az eredmenyeket. ");
    puts("TIPP: > -el lehet a kimenetet fajlba irni\n");

    puts("Teszt mod:");
    //puts("  -fa                 a modulok minden tesztje lefut, az is, ami");
    //puts("                      ezen a gepen esetleg lassu lehet");
    puts("  -n szam             ennyiszer fusson le a teszt (atlagol) n=[1..16]");
    puts("                      megbizhatobb eredmenyt kapsz, ha tobbszor fut le a teszt");
    puts("                      ha mast nem adsz meg, akkor n=3");
    puts("  -q                  csendes mod - ne irjon reszeredmenyeket, ha n>1");
    puts("  -name nev           nev vagy \"nev\" tetszoleges, pl.: \"Pistike win xp x64\"");
    puts("  -comment szoveg     szoveg (vagy \"szoveg\") tetszoleges megjegyszes");
    puts("  -o fajlnev          ebbe a fajlba menti az eredmenyt, nem a szabvanykimementre");

    puts("\nOsszehasonlito mod:");
    puts("  -cfg                ez alapjan hasonlitja ossze az eredmenyeket.");
    puts("                      ez legyen az elso parameter");
    puts("  -csv                .csv fajlba menti az eredment");
    puts("  -diff fajl1 fajl2.. osszehasonlito mod jelzese. ezutan a program");
    puts("                      minden parametert");
    puts("                      bemeneti fajlkent ertelmez");
    puts("Megjegyzes: ha a config fajlban nincs leiras egy tesztrol, akkor azt az erteket");
    puts("figyelmen kivul hagyja.");

    puts("\nConfig fajl generalasa:");
    puts("  -genconf            a config/generated.txt fajlba irja az eredmenyek alapjan a");
    puts("                      config fajlt. A sulyokat ugy allitja be, hogy minden");
    puts("                      modul es reszeredmeny azonos sullyal szerepeljen.");
    
    puts("\nConfig fajlok osszefesulese:");
    puts("  -mc fajl1 fajl2...  a megadott fajlokat atlagolja, a kimenet config/merged.txt");

    puts("\nPelda egy azonos gepen futo 32 bites MS Windows XP es Vista osszehasonlitasara:");
    puts("1. Windows XP-t inditod");
    puts("2. belepsz a pbench/binx86 konyvtarba");
    puts("3. pbench_Win32.exe -n 3 -o ../out/win32xp.txt");
    puts("4. ujrainditod a gepet, Windows Vista -val");
    puts("5. belepsz a pbench/binx86 konyvtarba");
    puts("6. pbench_Win32.exe -n 3 -o ../out/win32vista.txt");
    puts("7. pbench_Win32.exe -diff ../out/win32xp.txt ../out/win32vista.txt");    
}


static void AddBenchmarks(CBenchmark *bench)
{
    bench->Add(new CAlgorithmBenchmark);
    bench->Add(new CCpuBenchmark);
    bench->Add(new CNetBenchmark);
    bench->Add(new CThreadsBenchmark);
    bench->Add(new CMemoryBenchmark);
}


int main(int argc, const char *argv[])
{
#ifdef _MSC_VER
    _CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF|_CRTDBG_ALLOC_MEM_DF);
    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_WNDW | _CRTDBG_MODE_DEBUG | _CRTDBG_CHECK_ALWAYS_DF);
#endif

    CCmdlineArgs args;

    args.Init(argc, argv);
    //args.Init("pbench.exe -cfg config/test.txt -diff out/r2.txt out/r1.txt");
    //args.Init("pbench.exe -cfg config/test.txt -diff out/r1.txt out/r2.txt");
    //args.Init("pbench.exe -comment \"fakocka es alma\" -name hudejogep -n 2");    
    //args.Init("pbench.exe -genconf");
    //args.Init("pbench.exe -mc config/test.txt config/test2.txt");
    //args.Init("pbench.exe -cfg config/test.txt -csv -diff out/x/r1.txt out/x/r2.txt");

    if (args.Check("-?") || args.Check("-h") || args.Check("--help") || args.Check("/?") || args.Check("/h"))
    {
        PrintHelp();
        return 0;
    }

    if (args.Check("-diff"))
    {
        // tobb kulonbozo fajl/eredmeny osszehasonlitasa

        CResultManager resultman;

        if (!resultman.SetConfig(args.GetValue("-cfg", "config/default.txt")))
        {
            printf("Hiba: hibas vagy hianyzo config fajl\n");
            return -1;
        }

        bool loadfile = false;

        for (int i=1; i<args.GetSize(); i++)
        {
            if (loadfile)
            {
                if (resultman.LoadFile(args.GetAt(i)) == false)
                    printf("Hiba: nem tudtam betolteni: \"%s\"\n", args.GetAt(i));
            }
            else
            {
                if (strcmp(args.GetAt(i), "-diff") == 0)
                    loadfile = true;
            }
        }

        resultman.GenerateOutput(0);

        if (args.Check("-csv"))
            WriteCSV(&resultman);
        else
            WriteOutput(&resultman);
    }
    else if (args.Check("-mc"))
    {
        // tobb kulonbozo config fajl osszefesulese

        vector<CConfigFile*> results;           // jol jonne egy shared_ptr ...
        bool loadfile = false;

        for (int i=1; i<args.GetSize(); i++)
        {
            if (loadfile)
            {
                CConfigFile *conf = new CConfigFile;

                if (conf->Load(args.GetAt(i)) == false)
                {
                    printf("Hiba: hibas vagy hianyzo config fajl\n");
                    delete conf;
                    continue;
                }

                results.push_back(conf);
            }
            else
            {
                if (strcmp(args.GetAt(i), "-mc") == 0)
                    loadfile = true;
            }
        }

        CConfigFile::Merge(&results[0], (int)results.size(), "config/merged.txt");        

        for (uint i=0; i<results.size(); i++)
            delete results[i];
    }
    else if (args.Check("-genconf"))    // config fajl generealasa
    {
        int count = 5;
        CResultWriterMemory *results = new CResultWriterMemory[count];
        CResultWriterMemory avg_results;
        CBenchmark benchmark(&args);        
        RESULTSFILEINFO info;

        SetupInfo(&info, &args);
        info.comment += " -- CONFIG FILE";

        AddBenchmarks(&benchmark);

        for (int i=0; i<count; i++)
        {
            results[i].SetVerboseMode(true);
            benchmark.Run(&info, &results[i]);        }        puts("\n------------------------------------------------------------\n");        avg_results.SetVerboseMode(count > 1);        CResultWriterMemory::Merge(results, count, &avg_results);        avg_results.WriteConfig("config/generated.txt");                delete[] results;
    }
    else
    {
        // teszt

        int count = args.GetIntValue("-n", 1, 1, 16);
        bool verbose = count > 1 && !args.Check("-q");
        const char *outfilename = args.GetValue("-o", NULL);
        FILE *outfile = NULL;

        if (outfilename && (outfile=fopen(outfilename, "wt")) == NULL)
        {
            printf("Hiba: nem tudtam megnyitni a kimeneti fajlt: \"%s\"\n", outfilename);
            return -1;
        }

        if (outfile == NULL)
            outfile = stdout;        
        
        CResultWriterMemory *results = new CResultWriterMemory[count];
        CResultWriterFile avg_results(outfile);
        CBenchmark benchmark(&args);        
        RESULTSFILEINFO info;

        SetupInfo(&info, &args);

        AddBenchmarks(&benchmark);

        if (count > 1)
        {
            for (int i=0; i<count; i++)
            {
                results[i].SetVerboseMode(verbose);
                benchmark.Run(&info, &results[i]);            }            if (verbose)                puts("\n------========================-----\n");            CResultWriterMemory::Merge(results, count, &avg_results);        }        else        {            benchmark.Run(&info, &avg_results);        }                delete[] results;        if (outfile != stdout)            fclose(stdout);    }
}
